/**
 * humane.js
 * Humanized Messages for Notifications
 * @author Marc Harter (@wavded)
 * @example
 *   humane.log('hello world');
 * See more usage examples at: http://wavded.github.com/humane-js/
 */

;
!function (name, context, definition) {
    if (typeof module !== 'undefined') module.exports = definition(name, context)
    else if (typeof define === 'function' && typeof define.amd === 'object') define(definition)
    else context[name] = definition(name, context)
}('humane', this, function (name, context) {
    var win = window
    var doc = document

    var ENV = {
        on: function (el, type, cb) {
            'addEventListener' in win ? el.addEventListener(type, cb, false) : el.attachEvent('on' + type, cb)
        },
        off: function (el, type, cb) {
            'removeEventListener' in win ? el.removeEventListener(type, cb, false) : el.detachEvent('on' + type, cb)
        },
        bind: function (fn, ctx) {
            return function () {
                fn.apply(ctx, arguments)
            }
        },
        isArray: Array.isArray || function (obj) {
            return Object.prototype.toString.call(obj) === '[object Array]'
        },
        config: function (preferred, fallback) {
            return preferred != null ? preferred : fallback
        },
        transSupport: false,
        useFilter: /msie [678]/i.test(navigator.userAgent), // sniff, sniff
        _checkTransition: function () {
            var el = doc.createElement('div')
            var vendors = { webkit: 'webkit', Moz: '', O: 'o', ms: 'MS' }

            for (var vendor in vendors)
                if (vendor + 'Transition' in el.style) {
                    this.vendorPrefix = vendors[vendor]
                    this.transSupport = true
                }
        }
    }
    ENV._checkTransition()

    var Humane = function (o) {
        o || (o = {})
        this.queue = []
        this.baseCls = o.baseCls || 'humane'
        this.addnCls = o.addnCls || ''
        this.timeout = 'timeout' in o ? o.timeout : 2500
        this.waitForMove = o.waitForMove || false
        this.clickToClose = o.clickToClose || false
        this.timeoutAfterMove = o.timeoutAfterMove || false
        this.container = o.container

        try {
            this._setupEl()
        } // attempt to setup elements
        catch (e) {
            ENV.on(win, 'load', ENV.bind(this._setupEl, this)) // dom wasn't ready, wait till ready
        }
    }

    Humane.prototype = {
        constructor: Humane,
        _setupEl: function () {
            var el = doc.createElement('div')
            el.style.display = 'none'
            if (!this.container) {
                if (doc.body) this.container = doc.body;
                else throw 'document.body is null'
            }
            this.container.appendChild(el)
            this.el = el
            this.removeEvent = ENV.bind(function () {
                if (!this.timeoutAfterMove) {
                    this.remove()
                } else {
                    setTimeout(ENV.bind(this.remove, this), this.timeout);
                }
            }, this)
            this.transEvent = ENV.bind(this._afterAnimation, this)
            this._run()
        },
        _afterTimeout: function () {
            if (!ENV.config(this.currentMsg.waitForMove, this.waitForMove)) this.remove()

            else if (!this.removeEventsSet) {
                ENV.on(doc.body, 'mousemove', this.removeEvent)
                ENV.on(doc.body, 'click', this.removeEvent)
                ENV.on(doc.body, 'keypress', this.removeEvent)
                ENV.on(doc.body, 'touchstart', this.removeEvent)
                this.removeEventsSet = true
            }
        },
        _run: function () {
            if (this._animating || !this.queue.length || !this.el) return

            this._animating = true
            if (this.currentTimer) {
                clearTimeout(this.currentTimer)
                this.currentTimer = null
            }

            var msg = this.queue.shift()
            var clickToClose = ENV.config(msg.clickToClose, this.clickToClose)

            if (clickToClose) {
                ENV.on(this.el, 'click', this.removeEvent)
                ENV.on(this.el, 'touchstart', this.removeEvent)
            }

            var timeout = ENV.config(msg.timeout, this.timeout)

            if (timeout > 0)
                this.currentTimer = setTimeout(ENV.bind(this._afterTimeout, this), timeout)

            if (ENV.isArray(msg.html)) msg.html = '<ul><li>' + msg.html.join('<li>') + '</ul>'

            this.el.innerHTML = msg.html
            this.currentMsg = msg
            this.el.className = this.baseCls
            if (ENV.transSupport) {
                this.el.style.display = 'block'
                setTimeout(ENV.bind(this._showMsg, this), 50)
            } else {
                this._showMsg()
            }

        },
        _setOpacity: function (opacity) {
            if (ENV.useFilter) {
                try {
                    this.el.filters.item('DXImageTransform.Microsoft.Alpha').Opacity = opacity * 100
                } catch (err) {
                }
            } else {
                this.el.style.opacity = String(opacity)
            }
        },
        _showMsg: function () {
            var addnCls = ENV.config(this.currentMsg.addnCls, this.addnCls)
            if (ENV.transSupport) {
                this.el.className = this.baseCls + ' ' + addnCls + ' ' + this.baseCls + '-animate'
            }
            else {
                var opacity = 0
                this.el.className = this.baseCls + ' ' + addnCls + ' ' + this.baseCls + '-js-animate'
                this._setOpacity(0) // reset value so hover states work
                this.el.style.display = 'block'

                var self = this
                var interval = setInterval(function () {
                    if (opacity < 1) {
                        opacity += 0.1
                        if (opacity > 1) opacity = 1
                        self._setOpacity(opacity)
                    }
                    else clearInterval(interval)
                }, 30)
            }
        },
        _hideMsg: function () {
            var addnCls = ENV.config(this.currentMsg.addnCls, this.addnCls)
            if (ENV.transSupport) {
                this.el.className = this.baseCls + ' ' + addnCls
                ENV.on(this.el, ENV.vendorPrefix ? ENV.vendorPrefix + 'TransitionEnd' : 'transitionend', this.transEvent)
            }
            else {
                var opacity = 1
                var self = this
                var interval = setInterval(function () {
                    if (opacity > 0) {
                        opacity -= 0.1
                        if (opacity < 0) opacity = 0
                        self._setOpacity(opacity);
                    }
                    else {
                        self.el.className = self.baseCls + ' ' + addnCls
                        clearInterval(interval)
                        self._afterAnimation()
                    }
                }, 30)
            }
        },
        _afterAnimation: function () {
            if (ENV.transSupport) ENV.off(this.el, ENV.vendorPrefix ? ENV.vendorPrefix + 'TransitionEnd' : 'transitionend', this.transEvent)

            if (this.currentMsg.cb) this.currentMsg.cb()
            this.el.style.display = 'none'

            this._animating = false
            this._run()
        },
        remove: function (e) {
            var cb = typeof e == 'function' ? e : null

            ENV.off(doc.body, 'mousemove', this.removeEvent)
            ENV.off(doc.body, 'click', this.removeEvent)
            ENV.off(doc.body, 'keypress', this.removeEvent)
            ENV.off(doc.body, 'touchstart', this.removeEvent)
            ENV.off(this.el, 'click', this.removeEvent)
            ENV.off(this.el, 'touchstart', this.removeEvent)
            this.removeEventsSet = false

            if (cb && this.currentMsg) this.currentMsg.cb = cb
            if (this._animating) this._hideMsg()
            else if (cb) cb()
        },
        log: function (html, o, cb, defaults) {
            var msg = {}
            if (defaults)
                for (var opt in defaults)
                    msg[opt] = defaults[opt]

            if (typeof o == 'function') cb = o
            else if (o)
                for (var opt in o) msg[opt] = o[opt]

            msg.html = html
            if (cb) msg.cb = cb
            this.queue.push(msg)
            this._run()
            return this
        },
        spawn: function (defaults) {
            var self = this
            return function (html, o, cb) {
                self.log.call(self, html, o, cb, defaults)
                return self
            }
        },
        create: function (o) {
            return new Humane(o)
        }
    }
    return new Humane()
})
